package org.sean.framework.mybatis;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.sean.framework.context.SpringApplicationContext;
import org.sean.framework.redis.GzipRedisSerializer;
import org.sean.framework.redis.RedisService;
import org.sean.framework.util.DigestUtil;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 缓存
 */
@Slf4j
public class MybatisRedisCache implements Cache {

    private static final GzipRedisSerializer SERIALIZER = new GzipRedisSerializer();

    private RedisService redisService;
    private String id;

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }

    private Object execute(Function<RedisService, Object> fun) {
        try {
            if (redisService == null) {
                redisService = SpringApplicationContext.getBean(RedisService.class);
                if (redisService == null) {
                    return null;
                }
            }
            return fun.apply(redisService);
        } catch (Exception e) {
            log.warn("cache error", e);
            return null;
        }
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public int getSize() {
        Object result = execute(r -> r.getMapSize(convertId(id)));
        return result == null ? 0 : ((Long) result).intValue();
    }

    @Override
    public void putObject(final Object key, final Object value) {
        execute(r -> {
            long timeout = 300 * 1000L;
            if (value == null
                    || (value instanceof Collection && ((Collection<?>) value).isEmpty())
                    || (value instanceof Map<?, ?> && ((Map<?, ?>) value).isEmpty())) {
                timeout = 20 * 1000L;
            }

            byte[] data = SERIALIZER.serialize(value);
            // 超过10000 , 性能明显下降
            if (data.length > 0 && data.length < 10000) {
                r.putValueToMapWithGzip(convertId(id), convertKey(key.toString()), value, timeout, TimeUnit.MILLISECONDS);
            }
            return null;
        });
    }

    @Override
    public Object getObject(final Object key) {
        return execute(r -> r.getValueFromMapWithGzip(convertId(id), convertKey(key.toString())));
    }

    @Override
    public Object removeObject(final Object key) {
        return execute(r -> {
            r.deleteFromMap(convertId(id), convertKey(key.toString()));
            return null;
        });
    }

    @Override
    public void clear() {
        execute(r -> {
            r.delete(convertId(id));
            return null;
        });
    }

    private String convertId(String id) {
        return "mybatis:cache:" + id;
    }

    private String convertKey(String key) {
        return DigestUtil.md5Hex(key);
    }

    @Override
    public String toString() {
        return "Redis {" + id + "}";
    }
}
